昨天我們介紹了函式的基本結構與參數等等,接下來要來認識函式的多個樣貌了,我會逐一的介紹這些函式~
簡稱產生器(generator),是產生一組值的一種便利方式。而且它有一種獨特的特性,惰性(lazy)也就是說,只有要求產生器做事情的時候它才願意做事。也能產生無限串列( infinite list )
產生器函式:只有使用者要求時,才會計劃出下個數值。使用方式也很簡單,加上一個「*」就可以了。
function* fibbGenerator() { // No.1
let i = 0;
let j = 1;
while(true) {
yield i; // No.2
[i, j] = [j, i + j]; // NO.3
}
}
let fibb = fibbGenerator();
console.log(fibb.next()); // { value: 0, done: false }
console.log(fibb.next()); // { value: 1, done: false }
console.log(fibb.next()); // { value: 1, done: false }
console.log(fibb.next()); // { value: 2, done: false }
如果要宣告這是一個產生器,可以使用 IterableIterator
產生器式產生一連串值的一種方式,那怎麼一次一次的消耗它們,就得靠迭代器一代一代的消耗。聽起來有專業術語,我們先來定義並取例子吧。
Iterable(可迭代的):含有一個叫做 Symbol.iterator 的任何物件,其值是會回傳一個迭代器的函式。
Iterator(迭代器):定義有一個 next() 的任何物件,它會回傳含有 value 和 done 的一個物件。
從這裡開始,就來正式來學習怎麼表示這些函式的型別了。而首先就是先看看之前的常見範例。
function add(a: number, b: number): number {
return a + b;
}
請問 add 函式的型別為何呢? Function ?
對的,就像 Object 描述著所有的物件,Function 也描述著所有的函式,但我們還是得替他做一些定義型別的部分吧。
在 TypeScript 中,他會將 add 函式的型別推論成, (a: number, b:number) ⇒ number ,這就是用於函式型別的語法,稱之為呼叫特徵式( call signature )或是 型別特徵式( type signature ),可能會更貼近。
當然你可能會覺得他很像箭頭函式( arrow function ),這是 TypeScript 刻意的,這也是定型這個型別的語法。
呼叫特徵式只有型別層次( type-level )的程式碼,換句話說就是僅有型別沒有值。
型別層次 v.s. 值層次程式碼:基本上就是今天它是有效的 JavaScript 程式碼,那他就是值層次的。
說了那麼多,還不然先上一個範例吧~
// 建立一個呼叫特徵式的型別,logFunction
type LogFunction = (message: string) => void; // void 是不回傳唷。 // No.1
// 開始建立一個函式,是屬於 logFunction 型別
let logFunction: LogFunction = (message) => { // No.2
console.log(message); // 不回傳,直接印出。
}
// 建立一個呼叫特徵式的型別,logFunction
type LogFunction = (message: string) => void; // void 是不回傳唷。 // No.1
// 開始建立一個函式,是屬於 logFunction 型別
let logFunction: LogFunction = (message) => { // No.2
console.log(message); // 不回傳,直接印出。
}
用一樣的例子,當我們已經在 LogFunction 這邊(No.1)宣告了,就代表 TypeScript 能在(No.2)的 message 去推理出是一個 string 型別。 而這個 TypeScript 自己推論的功能,就是情境式定型( Contextual Typing )。
之前的範例都是使用簡單版本的呼叫特徵式,當然也有完整版本;那有什麼差別?只有語法上的差異而已,但是複雜版的會有一些特別的功能,在下面會介紹到。
簡單版呼叫特徵式
type Log = (message: string) => void
完整版呼叫特徵式
type Log = {
(message: string): void
}
完整版的會有什麼好處嗎?當然有的,其中第一個就是可以重載( overloading );假設想要設計預訂去日本的單程與來回機票的函式,我們可以這樣寫。
type Book = {
(from: Date, Destination: string): Booking,
(from: Date, to:Date, Destination: string): Booking,
}
let book: Book = (from, destination) => {
return true;
}
這邊就有介面( interface )的感覺在這邊,後續我們會開始介紹類別與介面~